using System.Collections.Generic;
using System.Numerics;
using System.Threading;

namespace Combina.Core
{
    public static class Factorial
    {
        public const int MaxN64 = 20;

        public static ulong Calculate64(int n)
        {
            return _factorials64[n];
        }

        public static BigInteger Calculate(int n)
        {
            if (n <= MaxN64)
                return Calculate64(n);

            _factorialsLock.EnterReadLock();
            try
            {
                if (n - MaxN64 < _factorials.Count)
                    return _factorials[n - MaxN64];
            }
            finally
            {
                _factorialsLock.ExitReadLock();
            }

            BigInteger value = n * Calculate(n - 1);
            _factorialsLock.EnterUpgradeableReadLock();
            try
            {
                if (n - MaxN64 >= _factorials.Count)
                {
                    _factorialsLock.EnterWriteLock();
                    try
                    {
                        _factorials.Add(value);
                    }
                    finally
                    {
                        _factorialsLock.ExitWriteLock();
                    }
                }
            }
            finally
            {
                _factorialsLock.ExitUpgradeableReadLock();
            }
            return value;
        }



        private static readonly ulong[] _factorials64 = new ulong[]
                                                            {
                                                                1,
                                                                1,
                                                                2,
                                                                6,
                                                                24,
                                                                120,
                                                                720,
                                                                5040,
                                                                40320,
                                                                362880,
                                                                3628800,
                                                                39916800,
                                                                479001600,
                                                                6227020800,
                                                                87178291200,
                                                                1307674368000,
                                                                20922789888000,
                                                                355687428096000,
                                                                6402373705728000,
                                                                121645100408832000,
                                                                2432902008176640000
                                                            };

        private static readonly List<BigInteger> _factorials =
            new List<BigInteger>(new[] { MaxN64 + 1 * ((BigInteger)Calculate64(MaxN64)) });
        private static readonly ReaderWriterLockSlim _factorialsLock = new ReaderWriterLockSlim();

    }
}